use codegraph_core::{
    Parser, LanguageAdapter, GraphQueryEngine, QueryBuilder, GraphQuery, 
    NodeType, EdgeType, QueryConfig,
};
use codegraph_parser::TreeSitterParser;
use codegraph_languages::PythonAdapter;

#[tokio::test]
async fn test_basic_query_execution() {
    let mut engine = GraphQueryEngine::new();
    let adapter = PythonAdapter::new();
    let parser = TreeSitterParser::python().expect("Failed to create Python parser");
    
    let python_code = r#"
import os
import sys

def hello_world():
    print("Hello, World!")

def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

class Calculator:
    def __init__(self):
        self.value = 0
    
    def add(self, x):
        self.value += x
        return self.value

calc = Calculator()
result = calc.add(42)
"#;

    // Parse and extract nodes/edges
    let tree = parser.parse(python_code, None).await.expect("Failed to parse code");
    let nodes = adapter.extract_nodes(tree.as_ref()).await.expect("Failed to extract nodes");
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n.as_ref()).collect();
    let edges = adapter.extract_edges(tree.as_ref(), &node_refs).await.expect("Failed to extract edges");
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e.as_ref()).collect();

    // Test basic query - find all functions
    let query = GraphQuery {
        node_types: Some(vec![NodeType::Function]),
        edge_types: None,
        languages: None,
        name_pattern: None,
        location_filter: None,
        metadata_filter: None,
        limit: None,
        offset: None,
    };

    let result = engine.execute_query(&query, &node_refs, &edge_refs).await
        .expect("Failed to execute query");

    println!("Basic query result:");
    println!("  Found {} nodes", result.nodes.len());
    println!("  Found {} edges", result.edges.len());
    println!("  Execution time: {}ms", result.execution_time_ms);

    // Test query with language filter
    let query_with_language = GraphQuery {
        node_types: None,
        edge_types: None,
        languages: Some(vec!["python".to_string()]),
        name_pattern: None,
        location_filter: None,
        metadata_filter: None,
        limit: Some(10),
        offset: None,
    };

    let result_with_language = engine.execute_query(&query_with_language, &node_refs, &edge_refs).await
        .expect("Failed to execute query with language filter");

    println!("\nQuery with language filter:");
    println!("  Found {} nodes", result_with_language.nodes.len());
    println!("  Execution time: {}ms", result_with_language.execution_time_ms);

    // Test query with name pattern
    let query_with_pattern = GraphQuery {
        node_types: None,
        edge_types: None,
        languages: None,
        name_pattern: Some("sample".to_string()),
        location_filter: None,
        metadata_filter: None,
        limit: None,
        offset: None,
    };

    let result_with_pattern = engine.execute_query(&query_with_pattern, &node_refs, &edge_refs).await
        .expect("Failed to execute query with pattern");

    println!("\nQuery with name pattern:");
    println!("  Found {} nodes", result_with_pattern.nodes.len());
    println!("  Execution time: {}ms", result_with_pattern.execution_time_ms);
}

#[tokio::test]
async fn test_advanced_query_builder() {
    let mut engine = GraphQueryEngine::new();
    let adapter = PythonAdapter::new();
    let parser = TreeSitterParser::python().expect("Failed to create Python parser");
    
    let code = r#"
def main():
    pass

class MyClass:
    def method(self):
        pass
"#;

    let tree = parser.parse(code, None).await.expect("Failed to parse code");
    let nodes = adapter.extract_nodes(tree.as_ref()).await.expect("Failed to extract nodes");
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n.as_ref()).collect();
    let edges = adapter.extract_edges(tree.as_ref(), &node_refs).await.expect("Failed to extract edges");
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e.as_ref()).collect();

    // Test QueryBuilder
    let builder = QueryBuilder::new()
        .with_node_types(vec![NodeType::Function, NodeType::Class])
        .with_languages(vec!["python".to_string()])
        .with_limit(5);

    let result = engine.execute_advanced_query(&builder, &node_refs, &edge_refs).await
        .expect("Failed to execute advanced query");

    println!("Advanced query result:");
    println!("  Found {} nodes", result.nodes.len());
    println!("  Found {} edges", result.edges.len());
    println!("  Found {} paths", result.paths.len());
    println!("  Total count: {}", result.total_count);
    println!("  Has more: {}", result.has_more);
    println!("  Execution time: {}ms", result.execution_time_ms);
    println!("  Aggregations: {:?}", result.aggregations);
}

#[tokio::test]
async fn test_shortest_path_finding() {
    let engine = GraphQueryEngine::new();
    let adapter = PythonAdapter::new();
    let parser = TreeSitterParser::python().expect("Failed to create Python parser");
    
    let code = r#"
def a():
    b()

def b():
    c()

def c():
    pass
"#;

    let tree = parser.parse(code, None).await.expect("Failed to parse code");
    let nodes = adapter.extract_nodes(tree.as_ref()).await.expect("Failed to extract nodes");
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n.as_ref()).collect();
    let edges = adapter.extract_edges(tree.as_ref(), &node_refs).await.expect("Failed to extract edges");
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e.as_ref()).collect();

    if nodes.len() >= 2 {
        let start_node = nodes[0].id();
        let end_node = nodes[1].id();

        let paths = engine.find_shortest_paths(start_node, end_node, &edge_refs, Some(5)).await
            .expect("Failed to find shortest paths");

        println!("Shortest path finding:");
        println!("  Found {} paths from {:?} to {:?}", paths.len(), start_node, end_node);
        
        for (i, path) in paths.iter().enumerate() {
            println!("  Path {}: {} nodes, {} edges, length {}, weight {:.2}", 
                     i + 1, path.nodes.len(), path.edges.len(), path.length, path.weight);
        }
    } else {
        println!("Not enough nodes for path finding test");
    }
}

#[tokio::test]
async fn test_query_caching() {
    let mut engine = GraphQueryEngine::with_config(QueryConfig {
        max_results: 100,
        max_depth: 5,
        enable_caching: true,
        timeout_ms: 1000,
    });
    
    let adapter = PythonAdapter::new();
    let parser = TreeSitterParser::python().expect("Failed to create Python parser");
    
    let code = r#"
def test():
    pass
"#;

    let tree = parser.parse(code, None).await.expect("Failed to parse code");
    let nodes = adapter.extract_nodes(tree.as_ref()).await.expect("Failed to extract nodes");
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n.as_ref()).collect();
    let edges = adapter.extract_edges(tree.as_ref(), &node_refs).await.expect("Failed to extract edges");
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e.as_ref()).collect();

    let query = GraphQuery {
        node_types: Some(vec![NodeType::Function]),
        edge_types: None,
        languages: None,
        name_pattern: None,
        location_filter: None,
        metadata_filter: None,
        limit: None,
        offset: None,
    };

    // First execution
    let start_time = std::time::Instant::now();
    let result1 = engine.execute_query(&query, &node_refs, &edge_refs).await
        .expect("Failed to execute first query");
    let first_duration = start_time.elapsed();

    // Second execution (should use cache)
    let start_time = std::time::Instant::now();
    let result2 = engine.execute_query(&query, &node_refs, &edge_refs).await
        .expect("Failed to execute second query");
    let second_duration = start_time.elapsed();

    println!("Query caching test:");
    println!("  First execution: {:?}", first_duration);
    println!("  Second execution: {:?}", second_duration);
    println!("  Results match: {}", result1.nodes == result2.nodes);

    // Test cache statistics
    let cache_stats = engine.get_cache_stats();
    println!("  Cache entries: {}", cache_stats.total_entries);
    println!("  Cache size: {} bytes", cache_stats.total_size_bytes);

    // Clear cache and verify
    engine.clear_cache();
    let cache_stats_after_clear = engine.get_cache_stats();
    println!("  Cache entries after clear: {}", cache_stats_after_clear.total_entries);
}

#[tokio::test]
async fn test_query_config() {
    // Test with custom configuration
    let config = QueryConfig {
        max_results: 5,
        max_depth: 3,
        enable_caching: false,
        timeout_ms: 500,
    };

    let mut engine = GraphQueryEngine::with_config(config);
    let adapter = PythonAdapter::new();
    let parser = TreeSitterParser::python().expect("Failed to create Python parser");
    
    let code = r#"
def func1(): pass
def func2(): pass
def func3(): pass
def func4(): pass
def func5(): pass
def func6(): pass
"#;

    let tree = parser.parse(code, None).await.expect("Failed to parse code");
    let nodes = adapter.extract_nodes(tree.as_ref()).await.expect("Failed to extract nodes");
    let node_refs: Vec<&dyn codegraph_core::CodeNode> = nodes.iter().map(|n| n.as_ref()).collect();
    let edges = adapter.extract_edges(tree.as_ref(), &node_refs).await.expect("Failed to extract edges");
    let edge_refs: Vec<&dyn codegraph_core::CodeEdge> = edges.iter().map(|e| e.as_ref()).collect();

    let query = GraphQuery {
        node_types: None,
        edge_types: None,
        languages: Some(vec!["python".to_string()]),
        name_pattern: None,
        location_filter: None,
        metadata_filter: None,
        limit: Some(10), // This should be limited by config.max_results
        offset: None,
    };

    let result = engine.execute_query(&query, &node_refs, &edge_refs).await
        .expect("Failed to execute query with custom config");

    println!("Query with custom config:");
    println!("  Found {} nodes (should be limited by config)", result.nodes.len());
    println!("  Execution time: {}ms", result.execution_time_ms);

    // Verify caching is disabled
    let cache_stats = engine.get_cache_stats();
    println!("  Cache entries (should be 0): {}", cache_stats.total_entries);
}
